package com.leon.hom.http;

import cn.hutool.core.lang.UUID;
import com.leon.hom.core.config.ListenConfig;
import com.leon.hom.core.log.Loggers;
import com.leon.hom.http.callback.HttpServerCallback;
import com.leon.hom.http.entity.HttpRequestParam;
import com.leon.hom.http.entity.HttpResponseParam;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.util.CharsetUtil;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;


public class HttpServer {

    private final NioEventLoopGroup bossGroup;

    private final NioEventLoopGroup workerGroup;


    private final Map<String, ChannelHandlerContext> sessionMap = new ConcurrentHashMap<>();

    public HttpServer(String agentTag, ListenConfig listenConfig, HttpServerCallback callback) {
        this.bossGroup = new NioEventLoopGroup();
        this.workerGroup = new NioEventLoopGroup();

        // 设置启动对象
        ServerBootstrap bootstrap = new ServerBootstrap();
        // 配置启动对象
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                                  @Override
                                  protected void initChannel(SocketChannel socketChannel) {
                                      // 获取管道
                                      ChannelPipeline pipeline = socketChannel.pipeline();
                                      pipeline.addLast("HttpResponseEncoder", new HttpResponseEncoder());
                                      pipeline.addLast("ReadTimeoutHandler", new ReadTimeoutHandler(30, TimeUnit.SECONDS));
                                      pipeline.addLast("WriteTimeoutHandler", new WriteTimeoutHandler(30, TimeUnit.SECONDS));
                                      pipeline.addLast("HttpRequestDecoder", new HttpRequestDecoder());
                                      pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(10 * 1024 * 1024));
                                      pipeline.addLast("HttpRequestHandler", new SimpleChannelInboundHandler<HttpObject>() {
                                          @Override
                                          protected void channelRead0(ChannelHandlerContext ctx, HttpObject httpObject) {

                                              FullHttpRequest request = (FullHttpRequest) httpObject;

                                              String sessionId = UUID.randomUUID().toString();
                                              sessionMap.put(sessionId, ctx);

                                              HttpHeaders headers = request.headers();
                                              Set<String> names = headers.names();
                                              HashMap<String, String> map = new HashMap<>();
                                              for (String name : names) {
                                                  map.put(name, headers.get(name));
                                              }

                                              HttpRequestParam requestParam = new HttpRequestParam();
                                              requestParam.setMethod(request.method().toString());
                                              requestParam.setUri(request.uri());
                                              requestParam.setSourceTag(agentTag);
                                              requestParam.setTargetTag(listenConfig.getTag());
                                              requestParam.setSourcePort(listenConfig.getPort());
                                              requestParam.setTargetPort(listenConfig.getTarget());
                                              requestParam.setSessionId(sessionId);
                                              requestParam.setHeaders(map);
                                              requestParam.setBody(request.content().toString(StandardCharsets.UTF_8));

                                              callback.handle(requestParam);
                                          }
                                      });
                                  }
                              }
                );

        bootstrap.bind(listenConfig.getPort());
        Loggers.HTTP.info("Listen HTTP port: {} , target port: {} , target tag: {}", listenConfig.getPort(), listenConfig.getTarget(), listenConfig.getTag());
    }

    public void close() {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    public void reply(HttpResponseParam responseParam) {
        ChannelHandlerContext ctx = sessionMap.get(responseParam.getSessionId());

        HttpVersion httpVersion = HttpVersion.valueOf(responseParam.getVersion().toUpperCase());
        ByteBuf byteBuf = Unpooled.copiedBuffer(responseParam.getBody(), CharsetUtil.UTF_8);
        HttpResponseStatus httpResponseStatus = HttpResponseStatus.valueOf(responseParam.getCode());

        DefaultFullHttpResponse defaultFullHttpResponse = new DefaultFullHttpResponse(httpVersion, httpResponseStatus, byteBuf);

        Map<String, String> headers = responseParam.getHeaders();
        if (null != headers) {
            headers.forEach((key, value) -> defaultFullHttpResponse.headers().set(key, value));
        }
        ctx.writeAndFlush(defaultFullHttpResponse);
        sessionMap.remove(responseParam.getSessionId());
    }


}
